﻿Imports System.Collections.Generic

' Neighbour finding optimized by skipping very many nodes which are "far enough" that they could never become near-neighbours
' Logic: If Node2 is distance D from Node1, we know that none of the next FLOOR(D/InsertNodeThresholdDistanceMetres)-1 nodes can be near neighbours to Node1
' This allows us to iteratively skip almost all nodes in the path when looking for neighbours to one node


Friend Class cNeighbourNodeFinderPathSkimmer
    Implements INeighbourNodeFinder

    Private Enum eOperatingMode
        eosfsnLeftFromSeekingNode
        eosfsnRightFromSeekingNode
    End Enum

    Private mPathNodeArray() As cFirePerimeterPathNode


    Friend Function MakeListOfNodesWithCloseNeighbours(ByVal FirePerimeterPath As FirePerimeterPath, ByVal NumberOfNodesInPath As Integer, ByVal InsertNodeThresholdDistanceMetres As Double, ByVal CrossSearchProximityThresholdDistanceMetres As Double) As List(Of cFirePerimeterPathNode) Implements INeighbourNodeFinder.MakeListOfNodesWithCloseNeighbours

        Dim NodesWithCloseNeighbours As List(Of cFirePerimeterPathNode)
        Dim CurrentNode As cFirePerimeterPathNode

        Me.InitializeArray(FirePerimeterPath, NumberOfNodesInPath)

        NodesWithCloseNeighbours = New List(Of cFirePerimeterPathNode)

        CurrentNode = FirePerimeterPath.FirstNode
        Do
            If CurrentNode.CutOperationStatus = cCutOperationTarget.eCutOperationStatus.ecosUntested Then
                CurrentNode.CutOperationStatus = cCutOperationTarget.eCutOperationStatus.ecosHasNoNeighbours
                Me.AddCloseNeighbours(CurrentNode, NumberOfNodesInPath, InsertNodeThresholdDistanceMetres, CrossSearchProximityThresholdDistanceMetres, NodesWithCloseNeighbours)
            End If
            CurrentNode = CurrentNode.NextNodeInPath
        Loop Until CurrentNode Is FirePerimeterPath.FirstNode

        Return NodesWithCloseNeighbours

    End Function


    Private Sub InitializeArray(ByVal Path As FirePerimeterPath, ByVal NumberOfNodesInPath As Integer)

        Dim CurrentIndexBaseOne As Integer
        Dim CurrentNode As cFirePerimeterPathNode

        ReDim mPathNodeArray(NumberOfNodesInPath)

        'Store nodes to the array
        CurrentIndexBaseOne = 1
        CurrentNode = Path.FirstNode
        Do
            mPathNodeArray(CurrentIndexBaseOne) = CurrentNode
            CurrentIndexBaseOne = CurrentIndexBaseOne + 1
            CurrentNode = CurrentNode.NextNodeInPath
        Loop Until CurrentNode Is Path.FirstNode

    End Sub


    Private Sub AddCloseNeighbours(ByVal CurrentNode As cFirePerimeterPathNode, ByVal NumberOfNodesInPath As Integer, ByVal InsertNodeThresholdDistanceMetres As Double, ByVal CrossSearchProximityThresholdDistanceMetres As Double, ByVal NodesWithCloseNeighbours As List(Of cFirePerimeterPathNode))

        Dim CurrentNodeIndexBaseOne As Integer
        Dim ComparingNodeIndexBaseOne As Integer
        Dim ComparingNode As cFirePerimeterPathNode
        Dim OperatingMode As eOperatingMode
        Dim CurrentDistance As Double
        Dim HowManyNodesCanBeJumpedOverPlusOne As Integer

        CurrentNodeIndexBaseOne = CurrentNode.ArrayIndexBaseOneForPathSkimmer

        ' Figure out the comparing node we want to start with
        If CurrentNodeIndexBaseOne < NumberOfNodesInPath Then
            ' In the array we have at least one node to the right from the seeking node
            OperatingMode = eOperatingMode.eosfsnRightFromSeekingNode
            ComparingNodeIndexBaseOne = CurrentNodeIndexBaseOne + 1
        Else
            ' In the array we have zero nodes to the right from the seeking node, so we start from array beginning
            OperatingMode = eOperatingMode.eosfsnLeftFromSeekingNode
            ComparingNodeIndexBaseOne = 1
        End If


        While True

            ComparingNode = mPathNodeArray(ComparingNodeIndexBaseOne)
            CurrentDistance = CurrentNode.GetDirectDistanceToSomeOtherNode(ComparingNode)

            If CurrentDistance < CrossSearchProximityThresholdDistanceMetres Then
                ' mark neighbours
                CurrentNode.CutOperationStatus = cCutOperationTarget.eCutOperationStatus.ecosHasNearbyNeighbours
                ComparingNode.CutOperationStatus = cCutOperationTarget.eCutOperationStatus.ecosHasNearbyNeighbours
                NodesWithCloseNeighbours.Add(CurrentNode)
                ' move scanner onwards by one
                ComparingNodeIndexBaseOne = ComparingNodeIndexBaseOne + 1
            Else
                ' If Node2 is distance D from Node1, we know that none of the next FLOOR(D/InsertNodeThresholdDistanceMetres)-1 nodes can be near neighbours to Node1
                ' So we can skip directly to next FLOOR(D/InsertNodeThresholdDistanceMetres) node which is first possible match index
                HowManyNodesCanBeJumpedOverPlusOne = CInt(Math.Floor(CurrentDistance / InsertNodeThresholdDistanceMetres))
                If HowManyNodesCanBeJumpedOverPlusOne = 0 Then
                    HowManyNodesCanBeJumpedOverPlusOne = 1
                End If
                ComparingNodeIndexBaseOne = ComparingNodeIndexBaseOne + HowManyNodesCanBeJumpedOverPlusOne
            End If

            ' Let's ensure that we wrap the array end/begin correctly
            If ComparingNodeIndexBaseOne > NumberOfNodesInPath Then
                If OperatingMode = eOperatingMode.eosfsnRightFromSeekingNode Then
                    ComparingNodeIndexBaseOne = ComparingNodeIndexBaseOne - NumberOfNodesInPath
                    OperatingMode = eOperatingMode.eosfsnLeftFromSeekingNode
                End If
            End If

            ' Let's stop when we have scanned the full array
            If OperatingMode = eOperatingMode.eosfsnLeftFromSeekingNode AndAlso ComparingNodeIndexBaseOne > CurrentNodeIndexBaseOne - 1 Then
                ' If we have reached current node or gone past it, we have scanned the full array and we can stop
                Exit While
            End If

        End While

    End Sub


End Class